iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
AI Engineering

30天拆Agent:從Repo看設計系列 第 8 篇

Day 8|HolmesGPT 的 Memory 有什麼特別?從 Checkpoint、Resume 到 Feedback

  • 分享至 

  • xImage
  •  

上一篇看 HolmesGPT 的 Tool 設計時,重點是 Model 這一輪到底有哪些能力可以用。

這一篇換成 Memory。

HolmesGPT 沒有另外包一個很抽象的 MemoryManager。真正讓 Agent 延續上下文的核心,其實就是送給 LLM 的:

messages

例如:

system
user
assistant
tool
assistant
user
...

其中ConversationWorker把原本只存在 LLM Context 裡的 messages,進一步變成:

可以保存的 Checkpoint
可以在 Human Gate 後 Resume 的 State
可以壓縮後繼續使用的 Memory
可以接受 Human Feedback 後立即修正

這篇就集中看這四件事。


先看整體架構:HolmesGPT 的 Memory 怎麼流動?

HolmesGPT 的 Conversation Memory 主線可以簡化成:

┌────────────────────────────────────┐
│ 使用者 / Frontend                   │
└─────────────────┬──────────────────┘
                  │ user_message
                  ▼
┌────────────────────────────────────┐
│ ConversationEvents                 │
│                                    │
│ 對話事件儲存                        │
│                                    │
│ holmes/core/supabase_dal.py        │
│ get_conversation_events()          │
└─────────────────┬──────────────────┘
                  │
                  ▼
┌────────────────────────────────────┐
│ ConversationWorker                 │
│                                    │
│ 對話執行與 Memory Restore           │
│                                    │
│ conversations_worker/worker.py     │
│ _hydrate_task_from_events()        │
└─────────────────┬──────────────────┘
                  │ conversation_history
                  ▼
┌────────────────────────────────────┐
│ build_chat_messages()              │
│                                    │
│ 組成本輪 messages                   │
│                                    │
│ holmes/core/conversations.py       │
└─────────────────┬──────────────────┘
                  │
                  ▼
┌────────────────────────────────────┐
│ ToolCallingLLM                     │
│                                    │
│ LLM → Tool → LLM                   │
│                                    │
│ holmes/core/tool_calling_llm.py    │
└─────────────────┬──────────────────┘
                  │
                  ▼
┌────────────────────────────────────┐
│ Terminal Event                     │
│                                    │
│ ai_answer_end                      │
│ approval_required                  │
│                                    │
│ 內含完整 messages Snapshot          │
└─────────────────┬──────────────────┘
                  │
                  ▼
┌────────────────────────────────────┐
│ ConversationEventPublisher         │
│                                    │
│ 寫回 ConversationEvents            │
│                                    │
│ event_publisher.py                 │
└─────────────────┬──────────────────┘
                  │
                  └────→ 下一輪重新 Restore

這張圖先記住三個角色就夠了:

  • messages:Agent 真正使用的 Memory。
  • ConversationWorker:把之前保存的 Memory 恢復回來。
  • Terminal Event:把目前的 messages 保存成新的 Checkpoint。

後面的 Approval、Compaction、Feedback,都是圍繞這份 messages 發生。


1. Terminal Event = Memory Checkpoint

HolmesGPT 第一個值得看的設計,是:

一輪 Agent 執行結束時,Terminal Event 會直接保存完整 messages。

關鍵 code 在:

holmes/core/conversations_worker/models.py

ConversationTask.conversation_history 的定義:

@property
def conversation_history(self):
    """Reconstructed from prior terminal events
    (ai_answer_end / approval_required)."""

真正恢復 History 的地方則在:

holmes/core/conversations_worker/worker.py
└── _hydrate_task_from_events()

核心邏輯:

messages = (ev.get("data") or {}).get("messages")

if messages:
    task.conversation_history = messages

也就是下一輪不是重新把所有 event 拼回 history,而是直接找到上一個 terminal event 裡保存的 messages。

具體例子

假設 SRE 第一輪問:

User:
checkout-api 在 production 一直 500,可以幫我查嗎?

Assistant:
先查 Pod。

Tool:
RestartCount = 6

Assistant:
再查 Logs。

Tool:
java.lang.OutOfMemoryError

Assistant:
再查 Deployment。

Tool:
memory limit = 512Mi

Assistant:
目前看起來是 OOM。

這一輪結束後:

ai_answer_end
        │
        └── messages
            ├─ User:checkout-api 一直 500
            ├─ Tool:RestartCount = 6
            ├─ Tool:OutOfMemoryError
            ├─ Tool:memory limit = 512Mi
            └─ Assistant:目前看起來是 OOM

下一輪:

User:
剛才那個服務重啟幾次?

        ↓

ConversationWorker
restore 上一個 ai_answer_end.messages

        ↓

Model 仍看得到:
RestartCount = 6

        ↓

Assistant:
6 次

所以 ai_answer_end 不只是「這輪回答完成」。

它同時是一份:

可以直接拿來開始下一輪的 Memory Snapshot。


2. approval_required = 可以 Resume 的 Execution Checkpoint

第二個特色,是 HolmesGPT 保存的不只是「聊過什麼」,還包含:

Agent 執行到哪裡。

假設前面的 checkout-api 調查結束後,Model 決定:

restart deployment checkout-api

但這個 Tool 需要 Human Approval。

關鍵 code 在:

holmes/core/tool_calling_llm.py

當 Tool 需要人工確認時,APPROVAL_REQUIRED 會帶:

{
    "messages": messages,
    "pending_approvals": [...],
    ...
}

所以它保存的不是一句:

等待使用者同意

而是:

approval_required
        │
        ├─ messages
        │   └─ 完整 Agent Context
        │
        └─ pending_approvals
            └─ restart_deployment(...)

ConversationWorker 也特別支援沒有新問題、只有 Tool Decision 的 Resume:

resume_only = bool(
    not ask and (
        data.get("tool_decisions")
        or data.get("frontend_tool_results")
    )
)

如果是 Resume:

if resume_only and chat_request.conversation_history:
    messages = list(chat_request.conversation_history)

具體例子

HolmesGPT 查到 checkout-api OOM
        ↓
Model 準備呼叫:
restart_deployment(
    namespace="production",
    deployment="checkout-api"
)
        ↓
approval_required
        ↓
保存:
messages + pending_approvals
        ↓
Human:Approve
        ↓
只送 tool_decisions
沒有新的 User Question
        ↓
ConversationWorker
Restore 原本 messages
        ↓
執行原本的 restart_deployment()
        ↓
Agent 繼續後面的處理

所以 HolmesGPT 的第二個特色可以濃縮成:

Conversation Memory 同時也是 Agent Execution State。

這對需要 Human Gate 的 Agent 特別重要。


3. Compaction = 產生下一版 Memory Snapshot

Agent 跑久之後,messages 一定會越來越長。

HolmesGPT 在:

holmes/core/truncation/input_context_window_limiter.py

每次 LLM Call 前都可以進入:

compact_if_necessary()

真正負責壓縮的是:

holmes/core/truncation/compaction.py
└── compact_conversation_history()

目前 Compaction 後會保留:

System Prompt
+
Conversation Summary
+
Last User Prompt

例如原本 History 已經包含:

checkout-api 500
Pod RestartCount = 6
Logs = OutOfMemoryError
memory limit = 512Mi
HPA max replicas = 3
Node memory pressure = false
...

Compaction 後可能變成:

System:
原本 HolmesGPT System Prompt

User:
Conversation summary:
- production checkout-api 持續 500
- Pod 已 Restart 6 次
- Logs 出現 OutOfMemoryError
- memory limit = 512Mi
- HPA max replicas = 3
- Node 沒有 memory pressure
- 目前推測為 container memory limit

User:
那為什麼昨天沒發生?

真正重要的是:壓縮後不只是拿來完成「這一次」LLM Call。

compact_if_necessary() 會發出:

conversation_history_compacted

其中直接包含:

"messages": compaction_result.messages_after_compaction

而:

holmes/core/conversations_worker/event_publisher.py

把它和另外兩種 Snapshot Event 放在一起:

_COMPACT_ON_FLUSH_EVENTS = {
    StreamEvents.ANSWER_END,
    StreamEvents.APPROVAL_REQUIRED,
    StreamEvents.CONVERSATION_HISTORY_COMPACTED,
}

具體例子

查了 Pod / Logs / HPA / Deployment / Node...
        ↓
messages 太長
        ↓
compact_conversation_history()
        ↓
產生 Summary
        ↓
重新組成較短的 messages
        ↓
conversation_history_compacted
        ↓
這份 compacted messages
成為新的 Memory Snapshot
        ↓
之後 Conversation 從這一版繼續

因此 HolmesGPT 的 Compaction 不只是:

減少 Token。

它同時也是:

Memory State Transition。

也就是 Model 現在看到的 History 被壓縮後,Backend 後面恢復的基準也一起更新。


4. Feedback = 「評估」和「當下修正」分成兩條路

HolmesGPT 的 Feedback 有兩種,而且用途完全不同。

這兩條不應該混在一起看。

4.1 Product Feedback:只進 Evaluation

第一種是一般產品常見的:

👍 / 👎
category
comment

每次 HolmesGPT request 都有自己的request_id, 用來將feedback跟對話關聯(相關 code:holmes/core/usage_recorder.py)

註解:

FE saves it from ai_answer_end
and passes it to the
record_feedback Supabase RPC later
when the user clicks 👍/👎.

holmes/core/supabase_dal.py 也明確寫:

feedback writes
(thumbs up/down + category + comment)
do NOT go through Holmes.

具體例子

HolmesGPT:
checkout-api 的主要問題是 memory limit 過低

        ↓

User:👎

Category:
Incorrect root cause

Comment:
昨晚剛部署新版,
也應該檢查 Deployment Change

        ↓

Frontend
record_feedback()

        ↓

Supabase

        ↓

Evaluation / Analytics

這份 Feedback 不會直接塞回 Agent Context。

所以這是一條:

Out-of-band Feedback。


4.2 Tool Feedback:直接回到 messages

另一種 Feedback 發生在 Tool Approval。

ToolApprovalDecision 裡有:

tool_call_id: str
approved: bool
feedback: Optional[str] = None

真正處理拒絕原因的地方是:

holmes/core/tool_calling_llm.py
└── _execute_tool_decisions()

如果使用者拒絕 Tool:

feedback_text = (
    f" User feedback: {tool_decision.feedback}"
    if tool_decision and tool_decision.feedback
    else ""
)

error_text = (
    f"Tool execution was denied by the user."
    f"{feedback_text}"
)

接著 Tool Result 會被轉成 LLM Message:

tool_call_message = tool_result.to_llm_message(...)

再插回:

messages.insert(...)

具體例子

Assistant:
我要 restart production checkout-api

        ↓

Human:Reject

Feedback:
Production 先不要 Restart,
先檢查昨晚 Deployment 的 image version

        ↓

Tool Result:
Tool execution was denied by the user.
User feedback:
Production 先不要 Restart,
先檢查昨晚 Deployment 的 image version

        ↓

這筆 Tool Result 插回 messages

        ↓

下一次 LLM Call

        ↓

Assistant:
先查看 Deployment revision
與 image version

Repo 的:

tests/test_tool_calling_llm.py

也直接檢查 Tool Message 是否包含:

User feedback: try using namespace kube-system instead

所以這條 Feedback 不是純紀錄。

它會直接改變 Agent 下一步看到的 observation。

這是一條:

In-band Corrective Feedback。


兩種 Feedback 的差別

                 User Feedback
                      │
          ┌───────────┴───────────┐
          │                       │
          ▼                       ▼
   Product Feedback         Tool Correction
          │                       │
   👍 / 👎 / comment        Reject + feedback
          │                       │
          ▼                       ▼
 record_feedback()       ToolApprovalDecision
          │                       │
          ▼                       ▼
      Supabase        _execute_tool_decisions()
          │                       │
          ▼                       ▼
Evaluation / Analytics       Tool Message
                                  │
                                  ▼
                               messages
                                  │
                                  ▼
                         下一次 Model Call

References


上一篇
Day 7|HolmesGPT 的 Tool 怎麼設計?
下一篇
Day 9|Codex Agent 架構:OpenAI 怎麼把 Model + Tool 做成一套 Runtime?
系列文
30天拆Agent:從Repo看設計 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言